package net.java.nioserver.utils;

import java.util.IdentityHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

import net.jcip.annotations.GuardedBy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Leonid Shlyapnikov
 */
public class BlockingPool<T> implements Pool<T> {

    private static final Logger log = LoggerFactory.getLogger(BlockingPool.class);
    
    @GuardedBy(value = "poolLock")
    private final T pool[];

    private final ReentrantLock poolLock = new ReentrantLock();

    private final Semaphore available;

    private final int poolCapacity;

    private final boolean debug;

    @GuardedBy(value = "poolLock")
    private final IdentityHashMap<T, T> takenResources;


    @GuardedBy(value = "poolLock")
    private int free;

    @SuppressWarnings("unchecked")
    public BlockingPool(int poolCapacity, ResourceFactory<T> resourceFactory, boolean debug) {
        this.poolCapacity = poolCapacity;
        this.debug = debug;

        if (debug) {
            takenResources = new IdentityHashMap<T, T>(poolCapacity);
        } else {
            this.takenResources = null;
        }

        this.pool = (T[]) new Object[poolCapacity];
        for (int indx = 0; indx < poolCapacity; indx++) {
            this.pool[indx] = resourceFactory.create();
        }

        this.available = new Semaphore(poolCapacity, true);
        this.free = poolCapacity;
    }

    public T take() throws InterruptedException {

        debugCheckResourceAvailability();
        available.acquire();
        
        poolLock.lock();
        try {
            T result = pool[--free];
            pool[free] = null;

            debugMarkResourceAsTaken(result);

            return result;

        } finally {
            poolLock.unlock();
        }
    }

    public void release(T buffer) {
        poolLock.lock();
        try {
            debugCheckResourceIsTaken(buffer);
            pool[free++] = buffer;
        } finally {
            poolLock.unlock();
        }
        available.release();
    }

    private final void debugMarkResourceAsTaken(T resource) {
        if (debug) {
            T existed = takenResources.put(resource, resource);
            if (existed != null) {
                throw new IllegalArgumentException("The resource already marked as taken: " + resource);
            }
        }
    }

    private final void debugCheckResourceAvailability() {
        if (debug) {
            if (0 == available.availablePermits()) {
                log.debug("HINT: availablePermits() == 0." +
                        " If you see this too often consider increasing pool size to improve performance.");
            }
        }
    }

    private final void debugCheckResourceIsTaken(T resource) {
        if (debug) {
            T removed = takenResources.remove(resource);
            if (removed != resource) {
                throw new IllegalArgumentException("The resource to be released does not belong to this pool: " + resource);
            }
        }
    }

    public String toString() {
        poolLock.lock();
        try {
            return toStringInternal();
        } finally {
            poolLock.unlock();
        }
    }

    private String toStringInternal() {
        return this.getClass().getSimpleName() + "{" +
                "poolCapacity=" + poolCapacity +
                ", free=" + free +
                ", available=" + available +
                ", debug=" + debug +
                '}';
    }

    public int getPoolCapacity() {
        return poolCapacity;
    }

    public int getFree() {
        poolLock.lock();
        try {
            return free;
        } finally {
            poolLock.unlock();
        }
    }

    public static interface ResourceFactory<T> {
        T create();
    }
}
